/**
 **************************************************************************
 * @file     iic_oled.c
 * @brief    iic_oled
 **************************************************************************
 *
 *
 **************************************************************************
 *
 */
#include <stdlib.h>
#include <string.h>
#include "at32f413.h"
#include "ssd1306.h"
#include "i2c_application.h"
#include "state_led.h"
#include "utils.h"
#include "xtp_type.h"


namespace OLED
{

    SSD1306::SSD1306(
        crm_periph_clock_type _iic_periph_clock,
        crm_periph_clock_type _sda_periph_clock,
        crm_periph_clock_type _clk_periph_clock,
        i2c_type* _iicx,
        uint8_t _addr,
        i2c_handle_type* _hi2cx,
        gpio_type* _clk_port,
        gpio_type* _sda_port,
        uint32_t _clk_pin,
        uint32_t _sda_pin
    )
    {
        m_iic_periph_clock = _iic_periph_clock;
        m_clk_periph_clock = _clk_periph_clock;
        m_sda_periph_clock = _sda_periph_clock;
        m_hi2cx = _hi2cx;
        m_iicx = _iicx;
        m_clk_port = _clk_port;
        m_sda_port = _sda_port;
        m_clk_pin = _clk_pin;
        m_sda_pin = _sda_pin;
        m_address = _addr;
        m_color = Color::White;
        m_iic_speed = 100000;
        m_inverted = false;
        m_current_x = 0;
        m_current_y = 0;
    }

    void SSD1306::init(void)
    {
        m_hi2cx->i2cx = m_iicx;
        i2c_config(m_hi2cx);

        m_send_command(0xAE);           //--turn off oled panel
        m_send_command(0x00);           //---set low column address
        m_send_command(0x10);           //---set high column address
        m_send_command(0x40);           //--set start line address  Set Mapping RAMssd1306.                              // Display Start Line (0x00~0x3F)
        m_send_command(0x81);           //--set contrast control register
        m_send_command(0xCF);           // Set SEG Output Current Brightness
        m_send_command(0xA1);           //--Set SEG/Column Mapping     0xa0左右反置 0xa1正常显示
        m_send_command(0xC8);           // Set COM/Row Scan Direction   0xc0上下反置 0xc8正常显示
        m_send_command(NORMAL_DISPLAY); //--set normal display
        m_send_command(0xA8);           //--set multiplex ratio(1 to 64)
        m_send_command(0x3f);           //--1/64 duty
        m_send_command(0xD3);           //-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
        m_send_command(0x00);           //-not offset
        m_send_command(0xd5);           //--set display clock divide ratio/oscillator frequency
        m_send_command(0x80);           //--set divide ratio, Set Clock as 100 Frames/Sec
        m_send_command(0xD9);           //--set pre-charge period
        m_send_command(0xF1);           // Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
        m_send_command(0xDA);           //--set com pins hardware configuration
        m_send_command(0x12);
        m_send_command(0xDB); //--set vcomh
        m_send_command(0x30); // Set VCOM Deselect Level
        m_send_command(0x20); //设置地址模式
        m_send_command(0x02); // 0x00：水平模式，页地址指针会自动增加，0x01：（垂直寻址模式），0x02：（页寻址）
        m_send_command(0x8D); //--set Charge Pump enable/disable
        m_send_command(0x14); //--set(0x10) disable
        clear();
        m_send_command(0xAF);
    }

    /**
     * @brief 发送 SSD1306 的命令，格式：地址（0x78)+模式（0x40）+要发送的命令
     * @param
     * @return
     */
    void SSD1306::m_send_command(u8 _command)
    {
        i2c_memory_write_dma(m_hi2cx, I2C_MEM_ADDR_WIDIH_8, m_address, kCmd, &_command, 1, kTimeOut);
    }

    /**
     * @brief 写数据
     * @param _data 要发送数据的地址
     * @param _size 要发送数据的大小
     * @return None
     */
    void SSD1306::m_send_data(u8* _data, u16 _size)
    {
        i2c_memory_write_dma(m_hi2cx, I2C_MEM_ADDR_WIDIH_8, m_address, kData, _data, _size, kTimeOut);
    }

    void SSD1306::clear(void)
    {
        memset(m_buffer, 0x00, kBufferSize);
    }

    u8 SSD1306::getAddress(void)
    {
        return m_address;
    }

    void SSD1306::update(void)
    {
        u8 i;
        for (i = 0; i < 8; i++)
        {
            m_send_command(0xB0 + i);
            m_send_command(0x00); //---set low column address
            m_send_command(0x10); //---set high column address
            m_send_data(&m_buffer[SSD1306::kSSD1306Width * i], SSD1306::kSSD1306Width);
        }
    }

    void SSD1306::fill(void)
    {
        u8 temp = m_color == Color::Black ? 0x00 : 0xFF;
        memset(m_buffer, temp, kBufferSize);
    }

    u32 SSD1306::getI2CSpeed(void)
    {
        return m_iic_speed;
    }

    void SSD1306::setI2CSpeed(u32 _speed)
    {
        m_iic_speed = _speed;
    }

    void SSD1306::setColor(Color _color)
    {
        m_color = _color;
    }

    Color SSD1306::getColor(void)
    {
        return m_color;
    }

    /**
     * @brief 正常显示/反色显示
     * @param ColorMode
     *        - ColorMode::NORMAL 正常显示
     *        - ColorMode::INVERT 反色显示
     * @return None
     */
    void SSD1306::setColorMode(ColorMode _color_mode)
    {
        //m_color_mode = _color_mode;

        switch (_color_mode)
        {
        case ColorMode::NORMAL:
            m_send_command(0xA6); // 正常显示
            break;
        case ColorMode::INVERT:
            m_send_command(0xA7); // 反色显示
            break;
        default:
            break;
        }
    }

    /**
     * @brief 打开显示
     * @param None
     * @return None
     */
    void SSD1306::on(void)
    {
        m_send_command(DISPLAY_ON);
    }

    /**
     * @brief 关闭显示
     * @param None
     * @return None
     */
    void SSD1306::off(void)
    {
        m_send_command(DISPLAY_OFF);
    }

    /**
     * @brief 显示旋转
     * @param DEG
     *        - DegMode::Deg_0   正常显示
     *        - DegMode::Deg_180 屏幕旋转180度
     *        - DegMode::Mirror  镜像显示
     * @return None
     */
    void SSD1306::setDegMode(DegMode _deg_mode)
    {
        m_deg_mode = _deg_mode;
        switch (_deg_mode)
        {
            // 正常显示
        case DegMode::Deg_0:
            m_send_command(0xC8); // 上下正常显示
            m_send_command(0xA1); // 水平正常显示
            break;
        case DegMode::Deg_180:
            // 旋转180度
            m_send_command(0xC0); // 上下翻转显示
            m_send_command(0xA0); // 水平反转显示
            break;
        case DegMode::Mirror:
            // 镜像显示
            m_send_command(0xC8); // 上下正常显示
            m_send_command(0xA0); // 水平反转显示
            break;
        default:
            break;
        }
    }

    void SSD1306::setInverted(bool _inverse)
    {
        m_inverted = _inverse;
    }

    /**
     * @brief 写像素,写 x与y 的交汇点，屏幕的其它像素保持原来的值
     *        - 如：要不第一行第一列的像素点亮
     * @param x x坐标 左右，x代表第几列 0~128
     * @param y y坐标 上下，y代表第几行，一个8页 0~64
     * @return None
     */
    void SSD1306::drawPixel(u8 x, u8 y)
    {
        Color color = m_color;

        if (m_inverted)
        {
            color = color == Color::Black ? Color::White : Color::Black;
        }

        if (color == Color::White)
        {
            m_buffer[x + (y / 8) * kSSD1306Width] |= 1 << (y % 8);
        }
        else
        {
            m_buffer[x + (y / 8) * kSSD1306Width] &= ~(1 << (y % 8));
        }
    }

    void SSD1306::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1)
    {
        int16_t steep = abs(y1 - y0) > abs(x1 - x0);
        if (steep)
        {
            Utils::swap_i16(x0, y0);
            Utils::swap_i16(x1, y1);
        }

        if (x0 > x1)
        {
            Utils::swap_i16(x0, x1);
            Utils::swap_i16(y0, y1);
        }

        int16_t dx, dy;
        dx = x1 - x0;
        dy = abs(y1 - y0);

        int16_t err = dx / 2;
        int16_t ystep;

        if (y0 < y1)
        {
            ystep = 1;
        }
        else
        {
            ystep = -1;
        }

        for (; x0 <= x1; x0++)
        {
            if (steep)
            {
                drawPixel(y0, x0);
            }
            else
            {
                drawPixel(x0, y0);
            }
            err -= dy;
            if (err < 0)
            {
                y0 += ystep;
                err += dx;
            }
        }
    }

    i2c_type* SSD1306::getIICx(void)
    {
        return m_iicx;
    }

    gpio_type* SSD1306::getClkPort(void)
    {
        return m_clk_port;
    }

    gpio_type* SSD1306::getSdaPort(void)
    {
        return m_sda_port;
    }

    uint32_t SSD1306::getClkPin(void)
    {
        return m_clk_pin;
    }

    uint32_t SSD1306::getSdaPin(void)
    {
        return m_sda_pin;
    }

    crm_periph_clock_type SSD1306::getI2CPeriphClock(void)
    {
        return m_iic_periph_clock;

    }
    crm_periph_clock_type SSD1306::getClkPeriphClock(void)
    {
        return m_clk_periph_clock;
    }

    crm_periph_clock_type SSD1306::getSdaPeriphClock(void)
    {
        return m_sda_periph_clock;
    }

    /**
     * @brief 绘制水平线
     * @param x 水平方向 0~128
     * @param y 垂直方向 0~64
     * @param length
     * @return None
     */
    void SSD1306::drawHorizontalLine(int16_t x, int16_t y, int16_t length)
    {
        if (y < 0 || y >= kSSD1306Height)
        {
            return;
        }

        if (x < 0)
        {
            length += x;
            x = 0;
        }

        if ((x + length) > kSSD1306Width)
        {
            length = (kSSD1306Width - x);
        }

        if (length <= 0)
        {
            return;
        }

        uint8_t* bufferPtr = m_buffer;
        bufferPtr += (y >> 3) * kSSD1306Width;
        bufferPtr += x;

        uint8_t drawBit = 1 << (y & 7);

        switch (m_color)
        {
        case Color::White:
            while (length--)
            {
                *bufferPtr++ |= drawBit;
            };
            break;
        case Color::Black:
            drawBit = ~drawBit;
            while (length--)
            {
                *bufferPtr++ &= drawBit;
            };
            break;
        case Color::Inverse:
            while (length--)
            {
                *bufferPtr++ ^= drawBit;
            };
            break;
        }
    }

    /**
     * @brief 绘制垂直线
     * @param x 水平方向 0~128
     * @param y 垂直方向 0~64
     * @param length
     * @return None
     */
    void SSD1306::drawVerticalLine(int16_t x, int16_t y, int16_t length)
    {
        if (x < 0 || x >= kSSD1306Width)
            return;

        if (y < 0)
        {
            length += y;
            y = 0;
        }

        if ((y + length) > kSSD1306Height)
        {
            length = (kSSD1306Height - y);
        }

        if (length <= 0)
            return;

        uint8_t yOffset = y & 7;
        uint8_t drawBit;
        uint8_t* bufferPtr = m_buffer;

        bufferPtr += (y >> 3) * kSSD1306Width;
        bufferPtr += x;

        if (yOffset)
        {
            yOffset = 8 - yOffset;
            drawBit = ~(0xFF >> (yOffset));

            if (length < yOffset)
            {
                drawBit &= (0xFF >> (yOffset - length));
            }

            switch (m_color)
            {
            case Color::White:
                *bufferPtr |= drawBit;
                break;
            case Color::Black:
                *bufferPtr &= ~drawBit;
                break;
            case Color::Inverse:
                *bufferPtr ^= drawBit;
                break;
            }

            if (length < yOffset)
                return;

            length -= yOffset;
            bufferPtr += kSSD1306Width;
        }

        if (length >= 8)
        {
            switch (m_color)
            {
            case Color::White:
            case Color::Black:
                drawBit = (m_color == Color::White) ? 0xFF : 0x00;
                do
                {
                    *bufferPtr = drawBit;
                    bufferPtr += kSSD1306Width;
                    length -= 8;
                } while (length >= 8);
                break;
            case Color::Inverse:
                do
                {
                    *bufferPtr = ~(*bufferPtr);
                    bufferPtr += kSSD1306Width;
                    length -= 8;
                } while (length >= 8);
                break;
            }
        }

        if (length > 0)
        {
            drawBit = (1 << (length & 7)) - 1;
            switch (m_color)
            {
            case Color::White:
                *bufferPtr |= drawBit;
                break;
            case Color::Black:
                *bufferPtr &= ~drawBit;
                break;
            case Color::Inverse:
                *bufferPtr ^= drawBit;
                break;
            }
        }
    }

    /**
     * @brief 绘制矩形
     * @param x
     * @param y
     * @param width
     * @param height
     * @return None
     */
    void SSD1306::drawRectangle(int16_t x, int16_t y, int16_t width, int16_t height)
    {
        drawHorizontalLine(x, y, width);
        drawVerticalLine(x, y, height);
        drawVerticalLine(x + width - 1, y, height);
        drawHorizontalLine(x, y + height - 1, width);
    }

    /**
     * @brief 绘制实心矩形
     * @param x
     * @param y
     * @param width
     * @param height
     * @return None
     */
    void SSD1306::drawFillRectangle(int16_t xMove, int16_t yMove, int16_t width, int16_t height)
    {
        for (int16_t x = xMove; x < xMove + width; x++)
        {
            drawVerticalLine(x, yMove, height);
        }
    }

    /**
     * @brief 显示空心三角形
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
    */
    void SSD1306::drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3)
    {
        /* Draw lines */
        drawLine(x1, y1, x2, y2);
        drawLine(x2, y2, x3, y3);
        drawLine(x3, y3, x1, y1);
    }

    /**
     * @brief 显示实心三角形
     * @param x1
     * @param y1
     * @param x2
     * @param y2
     * @param x3
     * @param y3
    */
    void SSD1306::drawFillTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3)
    {
        int16_t deltax = 0, deltay = 0, x = 0, y = 0, xinc1 = 0, xinc2 = 0,
            yinc1 = 0, yinc2 = 0, den = 0, num = 0, numadd = 0, numpixels = 0,
            curpixel = 0;

        deltax = abs(x2 - x1);
        deltay = abs(y2 - y1);
        x = x1;
        y = y1;

        if (x2 >= x1)
        {
            xinc1 = 1;
            xinc2 = 1;
        }
        else
        {
            xinc1 = -1;
            xinc2 = -1;
        }

        if (y2 >= y1)
        {
            yinc1 = 1;
            yinc2 = 1;
        }
        else
        {
            yinc1 = -1;
            yinc2 = -1;
        }

        if (deltax >= deltay)
        {
            xinc1 = 0;
            yinc2 = 0;
            den = deltax;
            num = deltax / 2;
            numadd = deltay;
            numpixels = deltax;
        }
        else
        {
            xinc2 = 0;
            yinc1 = 0;
            den = deltay;
            num = deltay / 2;
            numadd = deltax;
            numpixels = deltay;
        }

        for (curpixel = 0; curpixel <= numpixels; curpixel++)
        {
            drawLine(x, y, x3, y3);

            num += numadd;
            if (num >= den)
            {
                num -= den;
                x += xinc1;
                y += yinc1;
            }
            x += xinc2;
            y += yinc2;
        }
    }

    /**
     * @brief 显示空心圆
     * @param x0
     * @param y0
     * @param radius 圆形半径
    */
    void SSD1306::drawCircle(int16_t x0, int16_t y0, int16_t radius)
    {
        int16_t x = 0, y = radius;
        int16_t dp = 1 - radius;
        do
        {
            if (dp < 0)
                dp = dp + 2 * (++x) + 3;
            else
                dp = dp + 2 * (++x) - 2 * (--y) + 5;

            drawPixel(x0 + x, y0 + y);     //For the 8 octants
            drawPixel(x0 - x, y0 + y);
            drawPixel(x0 + x, y0 - y);
            drawPixel(x0 - x, y0 - y);
            drawPixel(x0 + y, y0 + x);
            drawPixel(x0 - y, y0 + x);
            drawPixel(x0 + y, y0 - x);
            drawPixel(x0 - y, y0 - x);

        } while (x < y);

        drawPixel(x0 + radius, y0);
        drawPixel(x0, y0 + radius);
        drawPixel(x0 - radius, y0);
        drawPixel(x0, y0 - radius);
    }

    /**
     * @brief 显示实心圆
     * @param x0
     * @param y0
     * @param radius 圆形的半径
    */
    void SSD1306::drawFillCircle(int16_t x0, int16_t y0, int16_t radius)
    {
        int16_t x = 0, y = radius;
        int16_t dp = 1 - radius;
        do
        {
            if (dp < 0)
            {
                dp = dp + 2 * (++x) + 3;
            }
            else
            {
                dp = dp + 2 * (++x) - 2 * (--y) + 5;
            }

            drawHorizontalLine(x0 - x, y0 - y, 2 * x);
            drawHorizontalLine(x0 - x, y0 + y, 2 * x);
            drawHorizontalLine(x0 - y, y0 - x, 2 * y);
            drawHorizontalLine(x0 - y, y0 + x, 2 * y);


        } while (x < y);
        drawHorizontalLine(x0 - radius, y0, 2 * radius);
    }

    void SSD1306::drawCircleQuads(int16_t x0, int16_t y0, int16_t radius, uint8_t quads)
    {
        int16_t x = 0, y = radius;
        int16_t dp = 1 - radius;
        while (x < y)
        {
            if (dp < 0)
                dp = dp + 2 * (++x) + 3;
            else
                dp = dp + 2 * (++x) - 2 * (--y) + 5;
            if (quads & 0x1)
            {
                drawPixel(x0 + x, y0 - y);
                drawPixel(x0 + y, y0 - x);
            }
            if (quads & 0x2)
            {
                drawPixel(x0 - y, y0 - x);
                drawPixel(x0 - x, y0 - y);
            }
            if (quads & 0x4)
            {
                drawPixel(x0 - y, y0 + x);
                drawPixel(x0 - x, y0 + y);
            }
            if (quads & 0x8)
            {
                drawPixel(x0 + x, y0 + y);
                drawPixel(x0 + y, y0 + x);
            }
        }
        if (quads & 0x1 && quads & 0x8)
        {
            drawPixel(x0 + radius, y0);
        }
        if (quads & 0x4 && quads & 0x8)
        {
            drawPixel(x0, y0 + radius);
        }
        if (quads & 0x2 && quads & 0x4)
        {
            drawPixel(x0 - radius, y0);
        }
        if (quads & 0x1 && quads & 0x2)
        {
            drawPixel(x0, y0 - radius);
        }
    }

    /**
     * @brief 显示进度条
     * @param x
     * @param y
     * @param width
     * @param height
     * @param progress
    */
    void SSD1306::drawProgressBar(uint16_t x, uint16_t y, uint16_t width, uint16_t height, uint8_t progress)
    {
        uint16_t radius = height / 2;
        uint16_t xRadius = x + radius;
        uint16_t yRadius = y + radius;
        uint16_t doubleRadius = 2 * radius;
        uint16_t innerRadius = radius - 2;

        setColor(Color::White);
        drawCircleQuads(xRadius, yRadius, radius, 0b00000110);
        drawHorizontalLine(xRadius, y, width - doubleRadius + 1);
        drawHorizontalLine(xRadius, y + height, width - doubleRadius + 1);
        drawCircleQuads(x + width - radius, yRadius, radius, 0b00001001);

        uint16_t maxProgressWidth = (width - doubleRadius + 1) * progress / 100;

        drawCircle(xRadius, yRadius, innerRadius);
        drawFillRectangle(xRadius + 1, y + 2, maxProgressWidth, height - 3);
        drawFillCircle(xRadius + maxProgressWidth, yRadius, innerRadius);
    }

    /**
     * @brief 显示单个字符
     * @param ch 要显示的字符
     * @param Font 选择字体 Font_7x10 | Font_11x18 | Font_16x26
    */
    char SSD1306::writeChar(char _ch, FontDef _font)
    {
        uint32_t i, b, j;

        // Check remaining space on current line
        if (kSSD1306Width <= (m_current_x + _font.FontWidth) ||
            kSSD1306Height <= (m_current_y + _font.FontHeight))
        {
            // Not enough space on current line
            return 0;
        }

        // Use the font to write
        for (i = 0; i < _font.FontHeight; i++)
        {
            b = _font.data[(_ch - 32) * _font.FontHeight + i];
            for (j = 0; j < _font.FontWidth; j++)
            {
                if ((b << j) & 0x8000)
                {
                    drawPixel(m_current_x + j, m_current_y + i);
                }
                else
                {
                    m_color = m_color == Color::Black ? Color::White : Color::Black;
                    drawPixel(m_current_x + j, m_current_y + i);
                    m_color = m_color == Color::Black ? Color::White : Color::Black;
                }
            }
        }

        // The current space is now taken
        m_current_x += _font.FontWidth;

        // Return written char for validation
        return _ch;
    }

    /**
     * @brief 显示字符串
     * @param _str 要显示的字符串指针
     * @param font 选择字体 Font_7x10 | Font_11x18 | Font_16x26
    */
    void SSD1306::writeString(std::string _str, FontDef _font)
    {
        // Write until null-byte
        // while (*str)
        // {
        //     if (writeChar(*str, Font) != *str)
        //     {
        //         // Char could not be written
                
        //     }

        //     // Next char
        //     str++;
        // }
        for (char c: _str)
        {
            writeChar(c, _font);
        }
    }

    /**
     * @brief 写整数
     * @param num 要显示的数字
     * @param Font 选择字体 Font_7x10 | Font_11x18 | Font_16x26
    */
    void SSD1306::writeNumber(int _num, FontDef _font)
    {
        char tmp[20];
        sprintf(tmp, "%d", _num);
        writeString(tmp, _font);
    }

    /**
     * @brief 写浮点数
     * @param _value 要显示的浮点数
     * @param font 选择字体 选择字体 Font_7x10 | Font_11x18 | Font_16x26
    */
    void SSD1306::writeFloat(float _value, FontDef _font)
    {
        char tmp[20];
        sprintf(tmp, "%.3f", _value);
        writeString(tmp, _font);
    }

    /**
     * @brief 设定像素光标位置
     * @param x
     * @param y
    */
    void SSD1306::setCursor(uint8_t x, uint8_t y)
    {
        m_current_x = x;
        m_current_y = y;
    }

    /**
     * @brief 写中文汉字，支持两种大小 16x16 和 24x24
     * @param size 字体大小，可选 16 或 24
     * @param index 字体索引
    */
    void SSD1306::writeChinese(uint8_t _size, uint8_t _index)
    {
        uint8_t i, j;
        uint8_t tmp;
        uint8_t x, y;
        x = m_current_x;
        y = m_current_y;
        if (_size == 16)
        {
            for (i = 0; i < 32;i++)
            {
                tmp = FontZH_16x16[_index][i];

                for (j = 0;j < 8;j++)
                {
                    if (tmp & 0x01) drawPixel(x, y);
                    tmp >>= 1;

                    x++;

                    if (x == (m_current_x + 16))
                    {
                        x = m_current_x; // 16x16 字体，像素打印顺序为一行16个像素打印完后打印下一行
                        y++;
                    }
                }
            }
            m_current_x += _size;
        }

        if (_size==24)
        {
            for (i = 0; i < 72;i++)
            {
                tmp = FontZH_24x24[_index][i];

                for (j = 0;j < 8;j++)
                {
                    if (tmp & 0x01) drawPixel(x, y);
                    tmp >>= 1;

                    x++;

                    if (x == (m_current_x + 24))
                    {
                        x = m_current_x; // 16x16 字体，像素打印顺序为一行16个像素打印完后打印下一行
                        y++;
                    }
                }
            }
            m_current_x += _size;
        }

    }

} /* namespace OLED */